package net.pulseproject.router.modules.logic.beatstabilizer.old.approach1;

import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Timer;

import javax.sound.midi.ShortMessage;

import net.pulseproject.commons.midi.Receivable;
import net.pulseproject.commons.midi.Transmitable;
import net.pulseproject.commons.midi.entity.ControllerMessage;
import net.pulseproject.commons.misc.TimerTaskWrapperX;
import net.pulseproject.commons.misc.TimerTaskWrapperX.TimerListener;
import net.pulseproject.router.modules.BiDirectionalModule;
import net.pulseproject.router.modules.logic.beatstabilizer.BeatStabilizerDebuggerGUiApp;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class OLD1_BeatStabilizerModule extends BiDirectionalModule implements TimerListener { // implements SimpleListener {
	
	private static final Log LOG = LogFactory.getLog(OLD1_BeatStabilizerModule.class);
	private static final DateFormat TARGET_TIME_FORMAT = new SimpleDateFormat("ss.SSS");
	
	private Date recentTargetTime;
	
	private final long globalMinMs;
//	/** jvm is not that exact with timer delays :-/*/
//	private static final long TIMER_DELAY_ADJUSTMENT = 5;
//	private final long globalMinMsAdjusted;
	private final int globalMaxMs;
	/** input from user, actually not used. e.g.: "2.5D"*/
	private final double frameDeviationPercent;
	
	/** e.g.: "0.025" */
	private final double frameDeviationPercentBy100;
	
	/** precalculated ready2use max multiplicator: e.g.: "1.025D"*/
	private final double frameDeviationMaxPercent;
	
	/** precalculated ready2use min multiplicator: e.g.: "0.075D"*/
	private final double frameDeviationMinPercent;
	
	
	private transient Timer timer;
	private transient long recentTickTime = -1;
	private transient int recentDurationMs = -1;
	
	public OLD1_BeatStabilizerModule(
			final String id,
			final Collection<Receivable> outgoingModules,
			final long globalMinMs, // LUXURY why the hack is there no such thing like type aliases in java?! :-/
			final int globalMaxMs,
			final double frameDeviationPercent
			) {
		super(id, outgoingModules);
		LOG.debug("new BeatStabilizerModule(globalMinMs=" + globalMinMs + ", globalMaxMs=" + globalMaxMs + ", frameDeviationPercent=" + frameDeviationPercent + ")");
		this.globalMinMs = globalMinMs;
//		this.globalMinMsAdjusted = globalMinMs - TIMER_DELAY_ADJUSTMENT;
		this.globalMaxMs = globalMaxMs;
		this.frameDeviationPercent = frameDeviationPercent;
		
		this.frameDeviationPercentBy100 = frameDeviationPercent / 100.0D;
		this.frameDeviationMaxPercent = 1.0D + this.frameDeviationPercentBy100;
		this.frameDeviationMinPercent = 1.0D - this.frameDeviationPercentBy100;
	}
	
	public void TODOOOO_ADD_INTERFACE_METHOD_stopTimer() {
		if(this.timer != null) {
			this.stopTimer();
		}
	}
	
	private void stopTimer() {
		this.timer.cancel();
		this.timer = null;
	}

	@Override
	public final void receive(final ShortMessage message, final Transmitable transmitable) {
		BeatStabilizerDebuggerGUiApp.DEBUG.blinkRawReceive();
		final Calendar c = Calendar.getInstance(); // quick take time snapshot
		final boolean isArtificialMessage = transmitable == this;
		LOG.info("receive() ... isArtificialMessage=" + isArtificialMessage);
		
		if(this.recentTickTime == -1) {
			LOG.fatal("  very first receive");
			this.recentTickTime = System.currentTimeMillis();
			this.broadcastToOutgoingModules(message);

			c.add(Calendar.MILLISECOND, this.globalMaxMs);
			this.delay(c.getTime());
			
		} else {
			final int probablyNewRecentDurationMs = (int) (System.currentTimeMillis() - this.recentTickTime);
			
			final int localMinMs = (int) Math.max(Math.round(this.recentDurationMs * this.frameDeviationMinPercent), this.globalMinMs);
			
			if(probablyNewRecentDurationMs < localMinMs) {
//			if(probablyNewRecentDurationMs < this.globalMinMs) {
				LOG.warn("  aborting ... probablyNewRecentDuration[" + probablyNewRecentDurationMs + "] < localMinMs["+localMinMs+"] (\"sending too fast\") ==> DROPPING!");
//				new Exception().printStackTrace();
				return;
			}
			
			LOG.debug("  sending signal ... recent duration: " + this.recentDurationMs + "");
			
			long recentTickTimeModifier = 0;
			if(isArtificialMessage == true) {
				// calculate back pivot for artificial messages (as it was only a lost signal from heartbeat sensor, and we can assume the human beat was stable meanwhile)
				recentTickTimeModifier = Math.round(this.recentDurationMs * this.frameDeviationPercentBy100);
			}
			
			this.recentDurationMs = probablyNewRecentDurationMs;
			this.recentTickTime = System.currentTimeMillis();
			this.recentTickTime += recentTickTimeModifier;
			
			this.TODOOOO_ADD_INTERFACE_METHOD_stopTimer();
			BeatStabilizerDebuggerGUiApp.DEBUG.blinkSent(isArtificialMessage);
			this.broadcastToOutgoingModules(message);
			final int localMaxMs;
			if(this.recentDurationMs == this.globalMaxMs) { // if already were on globalMax, deviation would exceed limit
				localMaxMs = this.globalMaxMs;
			} else {
				localMaxMs = (int) Math.round(this.recentDurationMs * this.frameDeviationMaxPercent);
				
			}
			BeatStabilizerDebuggerGUiApp.DEBUG.displayNextGreenFrameWindow("["+localMinMs+"-"+localMaxMs+"]");
			
			c.add(Calendar.MILLISECOND, localMaxMs);
			LOG.trace("invoking delay(maxMs = " + (this.recentDurationMs == this.globalMaxMs ? "globalMaxMs" : "recent["+this.recentDurationMs+"] * deviation["+this.frameDeviationMaxPercent+"]") + ")");
			this.delay(c.getTime());
		}
	}
	
	private void delay(Date targetTime) {
		LOG.debug("delay(targetTime=" + TARGET_TIME_FORMAT.format(targetTime) + ")");
		if(this.timer != null) {
			throw new RuntimeException("this.timer != null");
		}
		this.timer = new Timer();
		this.timer.schedule(new TimerTaskWrapperX(this), targetTime);
		
		this.recentTargetTime = targetTime;
	}
	
	@Override
	public void onTimerComplete() {
		LOG.warn("onTimerComplete() ... creating ARTIFICIAL MIDI message (\"sending to slow / none at all\")");
		
		// TODO create proper artifical MIDI message
		this.receive(new ControllerMessage(1, 1, 50).build(), this);
	}
	
}
